/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.cf.code;

import java.util.ArrayList;

import com.android.dx.rop.code.LocalItem;
import com.android.dx.rop.code.RegisterSpec;
import com.android.dx.rop.cst.Constant;
import com.android.dx.rop.type.Prototype;
import com.android.dx.rop.type.StdTypeList;
import com.android.dx.rop.type.Type;
import com.android.dx.rop.type.TypeBearer;

/**
 * Base implementation of {@link Machine}.
 * <p>
 * <b>Note:</b> For the most part, the documentation for this class ignores the
 * distinction between {@link Type} and {@link TypeBearer}.
 * </p>
 */
public abstract class BaseMachine implements Machine {

	/* {@code non-null;} the prototype for the associated method */
	private final Prototype prototype;

	/** {@code non-null;} primary arguments */
	private TypeBearer[] args;

	/** {@code >= 0;} number of primary arguments */
	private int argCount;

	/** {@code null-ok;} type of the operation, if salient */
	private Type auxType;

	/** auxiliary {@code int} argument */
	private int auxInt;

	/** {@code null-ok;} auxiliary constant argument */
	private Constant auxCst;

	/** auxiliary branch target argument */
	private int auxTarget;

	/** {@code null-ok;} auxiliary switch cases argument */
	private SwitchList auxCases;

	/** {@code null-ok;} auxiliary initial value list for newarray */
	private ArrayList<Constant> auxInitValues;

	/** {@code >= -1;} last local accessed */
	private int localIndex;

	/** specifies if local has info in the local variable table */
	private boolean localInfo;

	/** {@code null-ok;} local target spec, if salient and calculated */
	private RegisterSpec localTarget;

	/** {@code non-null;} results */
	private TypeBearer[] results;

	/**
	 * {@code >= -1;} count of the results, or {@code -1} if no results have
	 * been set
	 */
	private int resultCount;

	/**
	 * Constructs an instance.
	 * 
	 * @param prototype
	 *            {@code non-null;} the prototype for the associated method
	 */
	public BaseMachine(Prototype prototype) {
		if (prototype == null) {
			throw new NullPointerException("prototype == null");
		}

		this.prototype = prototype;
		args = new TypeBearer[10];
		results = new TypeBearer[6];
		clearArgs();
	}

	/** {@inheritDoc} */
	public Prototype getPrototype() {
		return prototype;
	}

	/** {@inheritDoc} */
	public final void clearArgs() {
		argCount = 0;
		auxType = null;
		auxInt = 0;
		auxCst = null;
		auxTarget = 0;
		auxCases = null;
		auxInitValues = null;
		localIndex = -1;
		localInfo = false;
		localTarget = null;
		resultCount = -1;
	}

	/** {@inheritDoc} */
	public final void popArgs(Frame frame, int count) {
		ExecutionStack stack = frame.getStack();

		clearArgs();

		if (count > args.length) {
			// Grow args, and add a little extra room to grow even more.
			args = new TypeBearer[count + 10];
		}

		for (int i = count - 1; i >= 0; i--) {
			args[i] = stack.pop();
		}

		argCount = count;
	}

	/** {@inheritDoc} */
	public void popArgs(Frame frame, Prototype prototype) {
		StdTypeList types = prototype.getParameterTypes();
		int size = types.size();

		// Use the above method to do the actual popping...
		popArgs(frame, size);

		// ...and then verify the popped types.

		for (int i = 0; i < size; i++) {
			if (!Merger.isPossiblyAssignableFrom(types.getType(i), args[i])) {
				throw new SimException("at stack depth " + (size - 1 - i)
						+ ", expected type " + types.getType(i).toHuman()
						+ " but found " + args[i].getType().toHuman());
			}
		}
	}

	public final void popArgs(Frame frame, Type type) {
		// Use the above method to do the actual popping...
		popArgs(frame, 1);

		// ...and then verify the popped type.
		if (!Merger.isPossiblyAssignableFrom(type, args[0])) {
			throw new SimException("expected type " + type.toHuman()
					+ " but found " + args[0].getType().toHuman());
		}
	}

	/** {@inheritDoc} */
	public final void popArgs(Frame frame, Type type1, Type type2) {
		// Use the above method to do the actual popping...
		popArgs(frame, 2);

		// ...and then verify the popped types.

		if (!Merger.isPossiblyAssignableFrom(type1, args[0])) {
			throw new SimException("expected type " + type1.toHuman()
					+ " but found " + args[0].getType().toHuman());
		}

		if (!Merger.isPossiblyAssignableFrom(type2, args[1])) {
			throw new SimException("expected type " + type2.toHuman()
					+ " but found " + args[1].getType().toHuman());
		}
	}

	/** {@inheritDoc} */
	public final void popArgs(Frame frame, Type type1, Type type2, Type type3) {
		// Use the above method to do the actual popping...
		popArgs(frame, 3);

		// ...and then verify the popped types.

		if (!Merger.isPossiblyAssignableFrom(type1, args[0])) {
			throw new SimException("expected type " + type1.toHuman()
					+ " but found " + args[0].getType().toHuman());
		}

		if (!Merger.isPossiblyAssignableFrom(type2, args[1])) {
			throw new SimException("expected type " + type2.toHuman()
					+ " but found " + args[1].getType().toHuman());
		}

		if (!Merger.isPossiblyAssignableFrom(type3, args[2])) {
			throw new SimException("expected type " + type3.toHuman()
					+ " but found " + args[2].getType().toHuman());
		}
	}

	/** {@inheritDoc} */
	public final void localArg(Frame frame, int idx) {
		clearArgs();
		args[0] = frame.getLocals().get(idx);
		argCount = 1;
		localIndex = idx;
	}

	/** {@inheritDoc} */
	public final void localInfo(boolean local) {
		localInfo = local;
	}

	/** {@inheritDoc} */
	public final void auxType(Type type) {
		auxType = type;
	}

	/** {@inheritDoc} */
	public final void auxIntArg(int value) {
		auxInt = value;
	}

	/** {@inheritDoc} */
	public final void auxCstArg(Constant cst) {
		if (cst == null) {
			throw new NullPointerException("cst == null");
		}

		auxCst = cst;
	}

	/** {@inheritDoc} */
	public final void auxTargetArg(int target) {
		auxTarget = target;
	}

	/** {@inheritDoc} */
	public final void auxSwitchArg(SwitchList cases) {
		if (cases == null) {
			throw new NullPointerException("cases == null");
		}

		auxCases = cases;
	}

	/** {@inheritDoc} */
	public final void auxInitValues(ArrayList<Constant> initValues) {
		auxInitValues = initValues;
	}

	/** {@inheritDoc} */
	public final void localTarget(int idx, Type type, LocalItem local) {
		localTarget = RegisterSpec.makeLocalOptional(idx, type, local);
	}

	/**
	 * Gets the number of primary arguments.
	 * 
	 * @return {@code >= 0;} the number of primary arguments
	 */
	protected final int argCount() {
		return argCount;
	}

	/**
	 * Gets the width of the arguments (where a category-2 value counts as two).
	 * 
	 * @return {@code >= 0;} the argument width
	 */
	protected final int argWidth() {
		int result = 0;

		for (int i = 0; i < argCount; i++) {
			result += args[i].getType().getCategory();
		}

		return result;
	}

	/**
	 * Gets the {@code n}th primary argument.
	 * 
	 * @param n
	 *            {@code >= 0, < argCount();} which argument
	 * @return {@code non-null;} the indicated argument
	 */
	protected final TypeBearer arg(int n) {
		if (n >= argCount) {
			throw new IllegalArgumentException("n >= argCount");
		}

		try {
			return args[n];
		} catch (ArrayIndexOutOfBoundsException ex) {
			// Translate the exception.
			throw new IllegalArgumentException("n < 0");
		}
	}

	/**
	 * Gets the type auxiliary argument.
	 * 
	 * @return {@code null-ok;} the salient type
	 */
	protected final Type getAuxType() {
		return auxType;
	}

	/**
	 * Gets the {@code int} auxiliary argument.
	 * 
	 * @return the argument value
	 */
	protected final int getAuxInt() {
		return auxInt;
	}

	/**
	 * Gets the constant auxiliary argument.
	 * 
	 * @return {@code null-ok;} the argument value
	 */
	protected final Constant getAuxCst() {
		return auxCst;
	}

	/**
	 * Gets the branch target auxiliary argument.
	 * 
	 * @return the argument value
	 */
	protected final int getAuxTarget() {
		return auxTarget;
	}

	/**
	 * Gets the switch cases auxiliary argument.
	 * 
	 * @return {@code null-ok;} the argument value
	 */
	protected final SwitchList getAuxCases() {
		return auxCases;
	}

	/**
	 * Gets the init values auxiliary argument.
	 * 
	 * @return {@code null-ok;} the argument value
	 */
	protected final ArrayList<Constant> getInitValues() {
		return auxInitValues;
	}

	/**
	 * Gets the last local index accessed.
	 * 
	 * @return {@code >= -1;} the salient local index or {@code -1} if none was
	 *         set since the last time {@link #clearArgs} was called
	 */
	protected final int getLocalIndex() {
		return localIndex;
	}

	/**
	 * Gets whether the loaded local has info in the local variable table.
	 * 
	 * @return {@code true} if local arg has info in the local variable table
	 */
	protected final boolean getLocalInfo() {
		return localInfo;
	}

	/**
	 * Gets the target local register spec of the current operation, if any. The
	 * local target spec is the combination of the values indicated by a
	 * previous call to {@link #localTarget} with the type of what should be the
	 * sole result set by a call to {@link #setResult} (or the combination
	 * {@link #clearResult} then {@link #addResult}.
	 * 
	 * @param isMove
	 *            {@code true} if the operation being performed on the local is
	 *            a move. This will cause constant values to be propagated to
	 *            the returned local
	 * @return {@code null-ok;} the salient register spec or {@code null} if no
	 *         local target was set since the last time {@link #clearArgs} was
	 *         called
	 */
	protected final RegisterSpec getLocalTarget(boolean isMove) {
		if (localTarget == null) {
			return null;
		}

		if (resultCount != 1) {
			throw new SimException("local target with "
					+ ((resultCount == 0) ? "no" : "multiple") + " results");
		}

		TypeBearer result = results[0];
		Type resultType = result.getType();
		Type localType = localTarget.getType();

		if (resultType == localType) {
			/*
			 * If this is to be a move operation and the result is a known
			 * value, make the returned localTarget embody that value.
			 */
			if (isMove) {
				return localTarget.withType(result);
			} else {
				return localTarget;
			}
		}

		if (!Merger.isPossiblyAssignableFrom(localType, resultType)) {
			// The result and local types are inconsistent. Complain!
			throwLocalMismatch(resultType, localType);
			return null;
		}

		if (localType == Type.OBJECT) {
			/*
			 * The result type is more specific than the local type, so use that
			 * instead.
			 */
			localTarget = localTarget.withType(result);
		}

		return localTarget;
	}

	/**
	 * Clears the results.
	 */
	protected final void clearResult() {
		resultCount = 0;
	}

	/**
	 * Sets the results list to be the given single value.
	 * <p>
	 * <b>Note:</b> If there is more than one result value, the others may be
	 * added by using {@link #addResult}.
	 * </p>
	 * 
	 * @param result
	 *            {@code non-null;} result value
	 */
	protected final void setResult(TypeBearer result) {
		if (result == null) {
			throw new NullPointerException("result == null");
		}

		results[0] = result;
		resultCount = 1;
	}

	/**
	 * Adds an additional element to the list of results.
	 * 
	 * @see #setResult
	 * @param result
	 *            {@code non-null;} result value
	 */
	protected final void addResult(TypeBearer result) {
		if (result == null) {
			throw new NullPointerException("result == null");
		}

		results[resultCount] = result;
		resultCount++;
	}

	/**
	 * Gets the count of results. This throws an exception if results were never
	 * set. (Explicitly clearing the results counts as setting them.)
	 * 
	 * @return {@code >= 0;} the count
	 */
	protected final int resultCount() {
		if (resultCount < 0) {
			throw new SimException("results never set");
		}

		return resultCount;
	}

	/**
	 * Gets the width of the results (where a category-2 value counts as two).
	 * 
	 * @return {@code >= 0;} the result width
	 */
	protected final int resultWidth() {
		int width = 0;

		for (int i = 0; i < resultCount; i++) {
			width += results[i].getType().getCategory();
		}

		return width;
	}

	/**
	 * Gets the {@code n}th result value.
	 * 
	 * @param n
	 *            {@code >= 0, < resultCount();} which result
	 * @return {@code non-null;} the indicated result value
	 */
	protected final TypeBearer result(int n) {
		if (n >= resultCount) {
			throw new IllegalArgumentException("n >= resultCount");
		}

		try {
			return results[n];
		} catch (ArrayIndexOutOfBoundsException ex) {
			// Translate the exception.
			throw new IllegalArgumentException("n < 0");
		}
	}

	/**
	 * Stores the results of the latest operation into the given frame. If there
	 * is a local target (see {@link #localTarget}), then the sole result is
	 * stored to that target; otherwise any results are pushed onto the stack.
	 * 
	 * @param frame
	 *            {@code non-null;} frame to operate on
	 */
	protected final void storeResults(Frame frame) {
		if (resultCount < 0) {
			throw new SimException("results never set");
		}

		if (resultCount == 0) {
			// Nothing to do.
			return;
		}

		if (localTarget != null) {
			/*
			 * Note: getLocalTarget() doesn't necessarily return localTarget
			 * directly.
			 */
			frame.getLocals().set(getLocalTarget(false));
		} else {
			ExecutionStack stack = frame.getStack();
			for (int i = 0; i < resultCount; i++) {
				if (localInfo) {
					stack.setLocal();
				}
				stack.push(results[i]);
			}
		}
	}

	/**
	 * Throws an exception that indicates a mismatch in local variable types.
	 * 
	 * @param found
	 *            {@code non-null;} the encountered type
	 * @param local
	 *            {@code non-null;} the local variable's claimed type
	 */
	public static void throwLocalMismatch(TypeBearer found, TypeBearer local) {
		throw new SimException("local variable type mismatch: "
				+ "attempt to set or access a value of type " + found.toHuman()
				+ " using a local variable of type " + local.toHuman()
				+ ". This is symptomatic of .class transformation tools "
				+ "that ignore local variable information.");
	}
}
